package com.tomz.xatomic;

import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 动态数据源注册
 * @author ZHUFEIFEI
 */
public class DynamicDataSourceRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private final Logger log = LoggerFactory.getLogger(getClass());

    private Environment env;

    /**
     * 配置别名设置
     */
    private final static ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();

    static {
        //其他数据源可能不叫url, 所以设置别名匹配
        aliases.addAliases("url", new String[]{"jdbc-url"});
        aliases.addAliases("username", new String[]{"user"});
    }

    /**
     * 参数绑定工具 springboot2.0新推出
     */
    private Binder binder;

    @Override
    public void setEnvironment(Environment environment) {
        this.env = environment;
        //获取参数绑定器，用于将参数与类属性进行绑定，就是赋值
        this.binder = Binder.get(environment);
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //将配置文件中的配置前缀为spring.datasources的 绑定到对象
        List<Map> configs = this.binder.bind(Constant.DATA_SOURCE_PROPERTY_PREFIX, Bindable.listOf(Map.class)).get();
        Class<? extends DataSource> clazz;
        DataSource ds;
        int index = 0;
        String name;
        Map<String, DataSource> dataSources = new HashMap<>();
        for (Map c : configs) {
            clazz = getDataSourceType((String) c.get("type"));
            ds = bind(clazz, c);
            name = c.getOrDefault("name", "datasource-" + index).toString();
            dataSources.put(name, ds);
            log.info("register dataSource : {} {}", name, clazz);
        }
        GenericBeanDefinition define = new GenericBeanDefinition();
        define.setBeanClass(DynamicRoutingDataSource.class);
        define.setPrimary(true);
        MutablePropertyValues pv = define.getPropertyValues();
        //DynamicRoutingDataSource类上的属性
        pv.addPropertyValue("defaultTargetDataSource", this.determineDefaultDataSource(dataSources));
        pv.addPropertyValue("targetDataSources", dataSources);
        registry.registerBeanDefinition(Constant.DATA_SOURCE_BEAN_NAME, define);
        log.info("dynamic dataSource init finished.");
    }

    private Object determineDefaultDataSource(Map<String, DataSource> dataSources) {
        if (dataSources.containsKey(Constant.DATA_SOURCE_DEFAULT_KEY)) {
            return dataSources.get(Constant.DATA_SOURCE_DEFAULT_KEY);
        }
        return dataSources.values().iterator().next();
    }

    private <T extends DataSource> T bind(Class<T> clazz, Map properties) {
        ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
        Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)});
        return binder.bind(ConfigurationPropertyName.EMPTY, Bindable.of(clazz)).get();
    }

    private Class<? extends DataSource> getDataSourceType(String type) {
        Class<? extends DataSource> clazz;
        try {
            if (StringUtils.hasLength(type)) {
                clazz = (Class<? extends DataSource>) Class.forName(type);
            } else {
                // 默认为Hikari数据源, spring-boot默认
                clazz = HikariDataSource.class;
            }
            return clazz;
        } catch (Exception e) {
            throw new IllegalArgumentException("can not resolve class with type: " + type);
        }
    }
}
